/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Class for handling simple settings - header.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#ifndef _OWNSETTINGS_HPP_
#define _OWNSETTINGS_HPP_

#include <vector>
#include <string>
#include <sstream>
#include <map>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h> 
#include "owndebug.h"
#include "ownutils.h"

namespace gim {

  /** Settings parser class. (Requires libxml2)
   *
   * This class is a container for tree-arranged settings.
   *
   * \todo Add support for Iterating through elements in any level.
   * \todo Add support for saving the memory-modified settings.
   *
   * The class supports multilevel keys, see 'settings_example.xml' 
   * for format example.
   *
   */
  class CSettings 
  {
    
  public:
    
    ///////////////////////////////////////////////////////////////////////////
    ////// Public Datatypes ///////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////

    /// Structure describing one key + data
    struct TKeyValue {
      TKeyValue() : 
        key(), data(), description() {
      }
      
      TKeyValue(const std::string &_key, 
                const std::string &_data, 
                const std::string &_desc = std::string("")) 
	: key(_key), data(_data), description(_desc) {
      }
      
      bool operator==(const std::string &_key) { 
        return key == _key; 
      }
      
      std::string key;            ///< Key to look for
      std::string data;           ///< Payload data
      std::string description;    ///< Value description
    };
    

    ///////////////////////////////////////////////////////////////////////////
    ////// Public interface  //////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////

    /**
     * CSettings empty constructor.
     * Initializes the Settings class
     */
    CSettings();


    /**
     * CSettings destructor
     * Cleans up Settings class
     */
    virtual ~CSettings();

    
    /**
     * Fetch a value, and copy its datafield as is.
     *
     * If zero-length string is given for 'key', a search for next
     * element with unique key is searched and returned (if available)
     *
     * @param[in] key           Key to look for
     * @param[out] data         Reference to string to receive the data.
     * @return                  true = when succesfull, the key was found
     *                          and copied.
     *                          false = the key was not found.
     */
    bool GetValue(const std::string &key, std::string &data) const;

    
    /**
     * Fetch a value, and attempt to convert it to unsigned integer.
     *
     * If zero-length string is given for 'key', a search for next
     * element with unique key is searched and returned (if available)
     *
     * @param[in] key           Key to look for
     * @param[out] data         Reference to integer receive the data.
     *                          (If the data is convertible to integer)
     * @return                  true = when succesfull, the key was found
     *                          and succesfully converted to unsigned 
     *                          integer.
     *                          false = the key was not found or the value
     *                          could not be converted to unsigned integer.
     */
    bool GetValue(const std::string &key, unsigned int &data) const;


    /**
     * Fetch a value, and attempt to convert it to signed integer.
     *
     * If zero-length string is given for 'key', a search for next
     * element with unique key is searched and returned (if available)
     * 
     * @param[in] key           Key to look for
     * @param[out] data         Reference to integer receiving the data.
     *                          (If the data is convertible to integer)
     * @return                  true = when succesfull, the key was found
     *                          and succesfully converted to integer.
     *                          false = the key was not found or the value
     *                          could not be converted to integer.
     */
    bool GetValue(const std::string &key, int &data) const;


    /**
     * Fetch a value, and attempt to convert it to double.
     *
     * If zero-length string is given for 'key', a search for next
     * element with unique key is searched and returned (if available)
     *
     * @param[in] key           Key to look for
     * @param[out] data         Reference to double to receive the data.
     *                          (If the data is convertible to double)
     * @return                  true = when succesfull, the key was found
     *                          and succesfully converted to double.
     *                          false = the key was not found or the value
     *                          could not be converted to double.
     */
    bool GetValue(const std::string &key, double &data) const;


    /** Fetch a value, and attempt to convert it to float.
     *
     * If zero-length string is given for 'key', a search for next
     * element with unique key is searched and returned (if available)
     * 
     * @param[in] key           Key to look for
     * @param[out] data         Reference to float receiving the data.
     *                          (If the data is convertible to float)
     * @return                  true = when succesfull, the key was found
     *                          and succesfully converted to double.
     *                          false = the key was not found or the value
     *                          could not be converted to double.
     */
    bool GetValue(const std::string &key, float &data) const {
      double v;

      // Read value using the 'double' method.
      const bool r = GetValue(key, v);
      
      // Convert the double value to float (loses a lot of precision)
      if (r) data = v;

      // Return the result of conversion.
      return r;
    }
    
    
    /**
     * Create new value from signed integer.
     *
     * @param[in] key           Key to store to (may pre-exist)
     * @param[in] data          Signed Integer to store
     * @param[in] replace       Replace element if previously exists, 
     *                          otherwise adds multiple entries.
     * @return                  true = success,
     *                          false = any failure.
     */
    bool SetValue(const std::string &key, const int data, bool replace = true);


    /**
     * Create new value from unsigned integer.
     *
     * @param[in] key           Key to store to (may pre-exist)
     * @param[in] data          Unsigned Integer to store
     * @param[in] replace       Replace element if previously exists, 
     *                          otherwise adds multiple entries.
     * @return                  true = success,
     *                          false = any failure.
     */
    bool SetValue(const std::string &key, const unsigned int data, bool replace = true);


    /**
     * Create new value from string.
     *
     * @param[in] key           Key to store to (may pre-exist)
     * @param[in] data          String to store
     * @param[in] replace       Replace element if previously exists, 
     *                          otherwise adds multiple entries.
     * @return                  true = success,
     *                          false = any failure.
     */
    bool SetValue(const std::string &key, const std::string &data, bool replace = true);


    /**
     * Create new value from double.
     *
     * @param[in] key           Key to store to (may pre-exist)
     * @param[in] data          Double value to store
     * @param[in] replace       Replace element if previously exists, 
     *                          otherwise adds multiple entries.
     * @return                  true = success,
     *                          false = any failure.
     */
    bool SetValue(const std::string &key, const double data, bool replace = true);


    /**
     * Sets a Base key. After setting the base key, all 'Get' and 'Set'
     * operations come from the selected keybranch. For example, if
     * you set the BaseKey to: 'Drivers.Bus', then GetValue("serial") would
     * yield in query for key: 'Drivers.Bus.serial'.
     *
     * @param[in] aBaseKey      Basekey to use
     * @return                  true = success
     *                          false = failure
     */
    bool SetBaseKey(const std::string &aBaseKey);


    /**
     * Returns the current Base key.
     *
     * @return                  Current Base key string.
     */
    std::string GetBaseKey(void) const;


    /** This reads in a XML settings file.
     *
     * This function automatically identified which format is used for
     * configuration - older 'Group' or newer 'Settings' format.
     *
     * @param[in] filename      Filename to read configuration from
     * @return                  true = success, file opened and parsed OK.
     *                          false = error, file open error or parse error.
     */
    bool ReadSettingsXMLFile(const std::string &filename);
    
  
    /** Write a configuration currently in memory to file.
     *
     * This function writes the current settings in memory to file in
     * the new 'Settings' format (instead of the older 'Group' format).
     * 
     * \note This function will overwrite the given file!
     *
     * @param[in] filename      Filename to write to. If this is
     *                          unspecified, the data is written
     *                          to file previously read by ReadSettingsXMLFile.
     *                          If this was never done, nothing is saved.
     * @return                  true = success, file saved.
     *                          false = error, couldn't use filename,
     *                          or some other error.
     */
    bool WriteSettingsXMLFile(const std::string &filename = std::string(""));


    /** This reads in a XML settings string.
     *
     * This function automatically identified which format is used for
     * configuration - older 'Group' or newer 'Settings' format.
     *
     * @param[in] string        std::string to read configuration from
     * @return                  true = success
     *                          false = error
     */
    bool ReadSettingsXMLString(const std::string xmldata);
    
    /** Write a configuration currently in memory to std::string.
     *
     * This function writes the current settings in memory to std::string in
     * the new 'Settings' format (instead of the older 'Group' format).
     * 
     *
     * @param[in] std::string   String to write to. 
     * @return                  true = success
     *                          false = error
     */
    bool WriteSettingsXMLString(std::string &strng);


    /**
     * Lock the settings class.
     */
    inline void Lock(void) {
      ownMutex_Lock(iSettingsMutex);
    }    
    

    /**
     * Unlock the Settings class.
     */
    inline void Unlock(void) {
      ownMutex_Unlock(iSettingsMutex);
    }
    
    
    /**
     * Returns whether we have any values defined or not.
     * @return                  True if we have any keys defined, otherwise
     *                          false.
     */
    inline bool IsEmpty(void) const {
      return iKeyValueArray.empty();
    }


    /**
     * Returns whether the queried key exists.
     *
     * @param[in] key           Key to query
     * @return                  true = key found,
     *                          false = key not found
     */
    inline bool IsKey(const std::string &key) const {
      return ( iKeyValueArray.find (key) != iKeyValueArray.end() );
    }

  protected:
    // internal Variables
    xmlDocPtr iSettingsDoc;              ///< Settings xml-documentpointer
    std::string iSettingsFilename;       ///< Used settings filename
    std::multimap<std::string, TKeyValue> iKeyValueArray; ///< Map of values
    std::string iBaseKey;                ///< BaseKey
    ownMutexHandle iSettingsMutex;       ///< Settings protecting mutex
    mutable std::string iLastSearchedKey;        ///< Last searched key.
    mutable std::multimap<std::string, TKeyValue>::const_iterator iLastSearchedKeyIterator; ///< Last searched found key iterator.

    // Internal Functions for parsing the settings file (XML)
    bool parseValue(const std::string &group, xmlNodePtr valuenode);
    bool parseGroup(const std::string &group, xmlNodePtr groupnode);
    bool parseSet(xmlNodePtr setnode);
    bool decodeSettingsFormat(xmlNodePtr groupnode);
    bool decodeGroupFormat(xmlNodePtr groupnode);
    void UpdateIterator(const std::string &key) const;

  private:
    // Copy constructor (Not allowed, therefore defined as private)
    CSettings(const CSettings &) : iSettingsDoc(NULL),
				   iSettingsFilename(),
				   iKeyValueArray(),
				   iBaseKey(),
				   iSettingsMutex(),
                                   iLastSearchedKey(),
                                   iLastSearchedKeyIterator() {}
    CSettings &operator=(const CSettings &) {return *this;}
  };    
}
#endif

/******************************************************************************
 *
 ******************************************************************************
 *
 * $Log: not supported by cvs2svn $
 * Revision 1.14  2009-10-14 09:39:30  mmatusia
 * Added a function for writing a settings to a std::string
 *
 * Revision 1.13  2009-05-13 07:18:25  amaula
 * Doxygens file tag parameter removed, as no parameter means 'document this file'
 *
 * Revision 1.12  2009-03-10 10:53:55  amaula
 * Documentation updated a bit.
 *
 * Revision 1.11  2009-03-10 10:35:28  amaula
 * Implemented new dataformat and support for iterative read.
 *
 * Revision 1.10  2009-03-10 10:34:38  amaula
 * Implemented new dataformat and support for iterative read.
 *
 * Revision 1.9  2008-12-02 13:21:31  amaula
 * Added overloaded method for 'float' datatype
 *
 * Revision 1.8  2008-10-29 10:25:40  mmatusia
 * robotMath.c & .h
 * Library for common robotics functions.
 *
 * Revision 1.7  2008-03-18 07:47:30  amaula
 * Minor fixes, mostly to remove Doxygen warnings
 *
 * Revision 1.6  2008-03-04 13:04:11  amaula
 * Commented out a unused parameter
 *
 * Revision 1.5  2008-02-13 12:20:59  amaula
 * Documentation updates.
 *
 * Revision 1.4  2007-04-18 08:50:41  amaula
 * Added method to query the current Base key.
 *
 * Revision 1.3  2007/03/13 11:11:02  amaula
 * Documentation updated.
 *
 * Revision 1.2  2007/03/13 10:36:53  amaula
 * - Support for 'BaseKey' setting.
 * - Support for Mutex for protecting the Settings class when used
 *   in multithreaded environment.
 *
 * Revision 1.1  2007/03/02 13:18:05  amaula
 * Initial import of settings. Version now 0.1
 *
 *
 *****************************************************************************/
